package com.ruyiadmin.springcloud.producer.service.impls.system;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.ruyiadmin.springcloud.producer.common.beans.system.QuartzConfig;
import com.ruyiadmin.springcloud.producer.common.beans.system.SystemCacheConfig;
import com.ruyiadmin.springcloud.producer.common.components.core.RuYiRedisComponent;
import com.ruyiadmin.springcloud.producer.common.core.business.enums.JobStatus;
import com.ruyiadmin.springcloud.producer.common.exceptions.RuYiAdminCustomException;
import com.ruyiadmin.springcloud.producer.domain.entity.system.SysScheduleJob;
import com.ruyiadmin.springcloud.producer.repository.system.ISysScheduleJobRepository;
import com.ruyiadmin.springcloud.producer.service.iservices.system.ISysScheduleJobService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * <p>
 * 计划任务表 服务实现类
 * </p>
 *
 * @author RuYiAdmin
 * @since 2022-07-12
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SysScheduleJobServiceImpl extends ServiceImpl<ISysScheduleJobRepository, SysScheduleJob>
        implements ISysScheduleJobService {

    //region 实现类私有属性

    private final RuYiRedisComponent redisUtils;
    private final SystemCacheConfig systemCacheConfig;
    private final QuartzConfig quartzConfig;
    private final ISysScheduleJobRepository scheduleJobRepository;
    private final Scheduler scheduler;

    //endregion

    //region 启动计划任务

    /**
     * 启动计划任务
     *
     * @param jobId  计划任务编号
     * @param userId 用户编号
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void startScheduleJob(String jobId, String userId) {
        Object value = this.redisUtils.get(systemCacheConfig.getScheduleJobCacheName());
        List<SysScheduleJob> scheduleJobs = JSON.parseArray(value.toString(), SysScheduleJob.class);

        List<SysScheduleJob> list = scheduleJobs.stream().
                filter(t -> t.getId().equals(jobId)).
                collect(Collectors.toList());
        if (list.size() > 0) {
            SysScheduleJob scheduleJob = list.get(0);

            //region 预设运行时间

            if (scheduleJob.getStartTime() == null) {
                scheduleJob.setStartTime(DateUtil.date());
            }

            if (scheduleJob.getEndTime() == null) {
                scheduleJob.setEndTime(new Date(Long.MAX_VALUE));
            }

            //endregion

            String jobName = scheduleJob.getJobName();
            String jobGroup = this.quartzConfig.getScheduleJobGroup();
            String jobTrigger = this.quartzConfig.getScheduleJobTrigger() + "/" + jobName;

            try {
                // 构建JobDetail
                JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>)
                        Class.forName(scheduleJob.getNamespace()
                                .replace("com.ruyiadmin.springboot",
                                        "com.ruyiadmin.springcloud.producer")
                                + "." + scheduleJob.getJobImplement()))
                        .withIdentity(jobName, jobGroup)
                        .build();
                // 构建JobTrigger
                CronTrigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(jobTrigger, jobGroup)
                        .startAt(scheduleJob.getStartTime())
                        .endAt(scheduleJob.getEndTime())
                        .withSchedule(CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()))
                        .build();
                // 启动调度器
                scheduler.start();
                scheduler.scheduleJob(jobDetail, trigger);

                //更新任务状态
                scheduleJob.setJobStatus(JobStatus.Running.ordinal());
                if (!userId.equals(StringUtils.EMPTY)) {
                    scheduleJob.setModifier(userId);
                    scheduleJob.setModifyTime(LocalDateTimeUtil.now());
                    //scheduleJob.setVersionId(UUID.randomUUID().toString());
                }
                this.scheduleJobRepository.updateById(scheduleJob);

                //region 数据一致性维护

                //删除旧数据
                for (Iterator<SysScheduleJob> iterator = scheduleJobs.iterator(); iterator.hasNext(); ) {
                    SysScheduleJob element = iterator.next();
                    if (element.getId().equals(scheduleJob.getId())) {
                        iterator.remove();
                        break;
                    }
                }
                //添加新数据
                scheduleJobs.add(scheduleJob);
                //回写缓存
                this.redisUtils.set(systemCacheConfig.getScheduleJobCacheName(), JSON.toJSONString(scheduleJobs));

                //endregion

            } catch (SchedulerException | ClassNotFoundException e) {
                throw new RuYiAdminCustomException(e);
            }
        }
    }

    //endregion

    //region 暂停计划任务

    /**
     * 暂停计划任务
     *
     * @param jobId  计划任务编号
     * @param userId 用户编号
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void pauseScheduleJob(String jobId, String userId) {
        Object value = this.redisUtils.get(systemCacheConfig.getScheduleJobCacheName());
        List<SysScheduleJob> scheduleJobs = JSON.parseArray(value.toString(), SysScheduleJob.class);

        List<SysScheduleJob> list = scheduleJobs.stream().
                filter(t -> t.getId().equals(jobId)).
                collect(Collectors.toList());
        if (list.size() > 0) {
            SysScheduleJob scheduleJob = list.get(0);
            String jobName = scheduleJob.getJobName();
            String jobGroup = this.quartzConfig.getScheduleJobGroup();
            try {
                scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));

                //更新任务状态
                scheduleJob.setJobStatus(JobStatus.Stopped.ordinal());
                if (!userId.equals(StringUtils.EMPTY)) {
                    scheduleJob.setModifier(userId);
                    scheduleJob.setModifyTime(LocalDateTimeUtil.now());
                    //scheduleJob.setVersionId(UUID.randomUUID().toString());
                }
                this.scheduleJobRepository.updateById(scheduleJob);

                //region 数据一致性维护

                //删除旧数据
                for (Iterator<SysScheduleJob> iterator = scheduleJobs.iterator(); iterator.hasNext(); ) {
                    SysScheduleJob element = iterator.next();
                    if (element.getId().equals(scheduleJob.getId())) {
                        iterator.remove();
                        break;
                    }
                }
                //添加新数据
                scheduleJobs.add(scheduleJob);
                //回写缓存
                this.redisUtils.set(systemCacheConfig.getScheduleJobCacheName(), JSON.toJSONString(scheduleJobs));

                //endregion

            } catch (SchedulerException e) {
                throw new RuYiAdminCustomException(e);
            }
        }
    }

    //endregion

    //region 恢复计划任务

    /**
     * 恢复计划任务
     *
     * @param jobId  计划任务编号
     * @param userId 用户编号
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void resumeScheduleJob(String jobId, String userId) {
        Object value = this.redisUtils.get(systemCacheConfig.getScheduleJobCacheName());
        List<SysScheduleJob> scheduleJobs = JSON.parseArray(value.toString(), SysScheduleJob.class);

        List<SysScheduleJob> list = scheduleJobs.stream().
                filter(t -> t.getId().equals(jobId)).
                collect(Collectors.toList());
        if (list.size() > 0) {
            SysScheduleJob scheduleJob = list.get(0);
            String jobName = scheduleJob.getJobName();
            String jobGroup = this.quartzConfig.getScheduleJobGroup();

            try {
                scheduler.resumeJob(JobKey.jobKey(jobName, jobGroup));

                //更新任务状态
                scheduleJob.setJobStatus(JobStatus.Running.ordinal());
                if (!userId.equals(StringUtils.EMPTY)) {
                    scheduleJob.setModifier(userId);
                    scheduleJob.setModifyTime(LocalDateTimeUtil.now());
                    //scheduleJob.setVersionId(UUID.randomUUID().toString());
                }
                this.scheduleJobRepository.updateById(scheduleJob);

                //region 数据一致性维护

                //删除旧数据
                for (Iterator<SysScheduleJob> iterator = scheduleJobs.iterator(); iterator.hasNext(); ) {
                    SysScheduleJob element = iterator.next();
                    if (element.getId().equals(scheduleJob.getId())) {
                        iterator.remove();
                        break;
                    }
                }
                //添加新数据
                scheduleJobs.add(scheduleJob);
                //回写缓存
                this.redisUtils.set(systemCacheConfig.getScheduleJobCacheName(), JSON.toJSONString(scheduleJobs));

                //endregion

            } catch (SchedulerException e) {
                throw new RuYiAdminCustomException(e);
            }
        }
    }

    //endregion

    //region 删除计划任务

    /**
     * 删除计划任务
     *
     * @param jobId  计划任务编号
     * @param userId 用户编号
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void deleteScheduleJob(String jobId, String userId) {
        Object value = this.redisUtils.get(systemCacheConfig.getScheduleJobCacheName());
        List<SysScheduleJob> scheduleJobs = JSON.parseArray(value.toString(), SysScheduleJob.class);

        List<SysScheduleJob> list = scheduleJobs.stream().
                filter(t -> t.getId().equals(jobId)).
                collect(Collectors.toList());
        if (list.size() > 0) {
            SysScheduleJob scheduleJob = list.get(0);
            String jobName = scheduleJob.getJobName();
            String jobGroup = this.quartzConfig.getScheduleJobGroup();

            try {
                scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, jobGroup));
                scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, jobGroup));
                scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));

                //更新任务状态
                scheduleJob.setJobStatus(JobStatus.Stopped.ordinal());
                if (!userId.equals(StringUtils.EMPTY)) {
                    scheduleJob.setModifier(userId);
                    scheduleJob.setModifyTime(LocalDateTimeUtil.now());
                    scheduleJob.setVersionId(UUID.randomUUID().toString());
                }
                this.scheduleJobRepository.deleteById(scheduleJob);

                //region 数据一致性维护

                //删除旧数据
                for (Iterator<SysScheduleJob> iterator = scheduleJobs.iterator(); iterator.hasNext(); ) {
                    SysScheduleJob element = iterator.next();
                    if (element.getId().equals(scheduleJob.getId())) {
                        iterator.remove();
                        break;
                    }
                }
                //回写缓存
                this.redisUtils.set(systemCacheConfig.getScheduleJobCacheName(), JSON.toJSONString(scheduleJobs));

                //endregion

            } catch (SchedulerException e) {
                throw new RuYiAdminCustomException(e);
            }

        }
    }

    //endregion

    //region 启动系统计划任务

    /**
     * 启动系统计划任务
     */
    @Override
    public void startScheduleJob() {
        Object value = this.redisUtils.get(systemCacheConfig.getScheduleJobCacheName());
        List<SysScheduleJob> scheduleJobs = JSON.parseArray(value.toString(), SysScheduleJob.class);

        //支持集群作业
        if (this.quartzConfig.isSupportGroup()) {
            //仅加载本节点定时任务
            scheduleJobs = scheduleJobs.stream()
                    .filter(t -> t.getGroupId() != null)
                    .filter(t -> t.getGroupId().equals(this.quartzConfig.getGroupId()))
                    .collect(Collectors.toList());
        }

        scheduleJobs.forEach(scheduleJob -> {
            if (scheduleJob.getJobStatus().equals(JobStatus.Running.ordinal())) {
                //region 预设运行时间

                if (scheduleJob.getStartTime() == null) {
                    scheduleJob.setStartTime(DateUtil.date());
                }

                if (scheduleJob.getEndTime() == null) {
                    scheduleJob.setEndTime(new Date(Long.MAX_VALUE));
                }

                //endregion

                String jobName = scheduleJob.getJobName();
                String jobGroup = this.quartzConfig.getScheduleJobGroup();
                String jobTrigger = this.quartzConfig.getScheduleJobTrigger() + "/" + jobName;

                try {
                    // 构建JobDetail
                    JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>)
                            Class.forName(scheduleJob.getNamespace()
                                    .replace("com.ruyiadmin.springboot",
                                            "com.ruyiadmin.springcloud.producer")
                                    + "." + scheduleJob.getJobImplement()))
                            .withIdentity(jobName, jobGroup)
                            .build();
                    // 构建JobTrigger
                    CronTrigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity(jobTrigger, jobGroup)
                            .startAt(scheduleJob.getStartTime())
                            .endAt(scheduleJob.getEndTime())
                            .withSchedule(CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()))
                            .build();
                    // 启动调度器
                    scheduler.start();
                    scheduler.scheduleJob(jobDetail, trigger);

                } catch (SchedulerException | ClassNotFoundException e) {
                    throw new RuYiAdminCustomException(e);
                }
            }
        });
    }

    //endregion

    //region 加载计划任务缓存

    /**
     * 加载计划任务缓存
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void loadBusinessScheduleJobCache() {
        List<SysScheduleJob> jobs = this.list();
        this.redisUtils.set(systemCacheConfig.getScheduleJobCacheName(), JSON.toJSONString(jobs));
        log.info("RuYiAdmin sys schedule jobs cache loaded");
    }

    //endregion

    //region 清理计划任务缓存

    /**
     * 清理计划任务缓存
     */
    @Override
    public void clearBusinessScheduleJobCache() {
        this.redisUtils.del(systemCacheConfig.getScheduleJobCacheName());
        log.info("RuYiAdmin sys schedule jobs cache cleared");
    }

    //endregion

}
